# -*- coding: utf-8 -*-
# @Author: ystlong
# @Date:   2018-07-22 15:44:56
# @Last Modified by:   ystlong
# @Last Modified time: 2018-07-22 22:16:03


from twisted.internet import protocol, reactor, endpoints
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol
import struct

def print_byte(s):
    for c in s:
        print ord(c),

class Socks5End(protocol.Protocol):
    def __init__(self, socks5front, addr):
        self.socks5front = socks5front
        self.addr = addr

    def dataRecived(self, data):
        print "=====response====\n%s\n"%(data)
        # print_byte(data)
        print data
        print ""
        # self.socks5front.transport.resumeProducing()
        self.socks5front.transport.send(data)
        # print data

    # def send_data(self, data):
    #     print "=====send data===="
    #     self.transport.write(data)

    def connectionMade(self):
        print "=====echo connect*****"

        self.socks5front.socks5end = self

        # self.sendCommand(self.command, self.rest)
        # for header, value in self.headers.items():
        #     self.sendHeader(header, value)
        # self.endHeaders()


        # self.transport.write("dd")

        # self.factory.numProtocols = self.factory.numProtocols + 1
        # self.transport.write(
        #     "Welcome! There are currently %d open connections.\n" %
        #     (self.factory.numProtocols,))


        # Wire this and the peer transport together to enable
        # flow control (this stops connections from filling
        # this proxy memory when one side produces data at a
        # higher rate than the other can consume).
        self.transport.registerProducer(self.socks5front.transport, True)
        self.socks5front.transport.registerProducer(self.transport, True)

        # We're connected, everybody can read to their hearts content.
        self.socks5front.transport.resumeProducing()

        # print self.transport.getHost()
        # print dir(self.transport.getHost().host)
        self.socks5front.target_connect_ok(self.transport.getHost())

        print "==========client connect ok====="
        pass

    def connectionLost(self, reason):
        # self.factory.numProtocols = self.factory.numProtocols - 1
        pass

class Socks5EndFactory(ClientFactory):
    def __init__(self, socks5front):
        # self.Socks5End = None
        self.socks5front = socks5front

    # def send_data(self, data):
    #     self.Socks5End.send_data(data)
        # self.transport.write(data)

    def startedConnecting(self, connector):
        print('===Started to connect.')

    def buildProtocol(self, addr):
        print('===Connected.', addr)
        # self.Socks5End = Socks5End()
        # return self.Socks5End
        return Socks5End(self.socks5front, addr)

    def clientConnectionLost(self, connector, reason):
        print('====Lost connection.  Reason:', reason)

    def clientConnectionFailed(self, connector, reason):
        print('===Connection failed. Reason:', reason)

class Socks5Front(protocol.Protocol):
    def __init__(self, factory, addr):
        self.factory = factory
        # self.Socks5Endfac = None
        self.ishansol = False
        self.socks5end = None
        self.socksok = False
        self.addr = addr

    def connectionMade(self):
        print "******echo connect*****"
        # self.factory.numProtocols = self.factory.numProtocols + 1
        # self.transport.write(
        #     "Welcome! There are currently %d open connections.\n" %
        #     (self.factory.numProtocols,))
        pass

    def connectionLost(self, reason):
        # self.factory.numProtocols = self.factory.numProtocols - 1
        pass

    def hansol(self, data):
        rep = '\x05\xFF'
        if len(data) < 3:
            print "error reequest"
        else:
            ver = ord(data[0])
            nmethod = ord(data[1])
            suSocks5Endort_method = 0
            if ver == 5:
                for i in xrange(nmethod):
                    print "find client suSocks5Endort method", ord(data[2+i])
                    if ord(data[2+i]) == suSocks5Endort_method:
                        print "find suSocks5Endort socks auth method"
                        rep = '\x05\x00'
                        break
        self.ishansol = True
        self.transport.write(rep)


    # **第二步**
    #   一旦方法选择子商议结束，客户机就发送请求细节。如果商议方法包括了完整性检查的目的或机密性封装
    #   ，则请求必然被封在方法选择的封装中。 

    #   SOCKS请求如下表所示:
    #   +----+-----+-------+------+----------+----------+ 
    #   | VER| CMD | RSV   | ATYP |  DST.ADDR|  DST.PORT|
    #   +----+-----+-------+------+----------+----------+ 
    #   | 1  | 1   | X'00' | 1    | variable |      2   |
    #   +----+-----+-------+------+----------+----------+ 

    #   各个字段含义如下:
    #   VER  版本号X'05'
    #   CMD：  
    #        1. CONNECT X'01'
    #        2. BIND    X'02'
    #        3. UDP ASSOCIATE X'03'
    #   RSV  保留字段
    #   ATYP IP类型 
    #        1.IPV4 X'01'
    #        2.DOMAINNAME X'03'
    #        3.IPV6 X'04'
    #   DST.ADDR 目标地址 
    #        1.如果是IPv4地址，这里是big-endian序的4字节数据
    #        2.如果是FQDN，比如"www.nsfocus.net"，这里将是:
    #          0F 77 77 77 2E 6E 73 66 6F 63 75 73 2E 6E 65 74
    #          注意，没有结尾的NUL字符，非ASCIZ串，第一字节是长度域
    #        3.如果是IPv6地址，这里是16字节数据。
    #   DST.PORT 目标端口（按网络次序排列） 
    # **sock5响应如下:**
    #  OCKS Server评估来自SOCKS Client的转发请求并发送响应报文:
    #  +----+-----+-------+------+----------+----------+
    #  |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
    #  +----+-----+-------+------+----------+----------+
    #  | 1  |  1  | X'00' |  1   | Variable |    2     |
    #  +----+-----+-------+------+----------+----------+
    #  VER  版本号X'05'
    #  REP  
    #       1. 0x00        成功
    #       2. 0x01        一般性失败
    #       3. 0x02        规则不允许转发
    #       4. 0x03        网络不可达
    #       5. 0x04        主机不可达
    #       6. 0x05        连接拒绝
    #       7. 0x06        TTL超时
    #       8. 0x07        不支持请求包中的CMD
    #       9. 0x08        不支持请求包中的ATYP
    #       10. 0x09-0xFF   unassigned
    def fetch_cmd(self, data):
        print "fetch cmd"
        rep = "\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00"
        # rep = "\x05\x05"
        issuSocks5Endort = False
        if len(data) < 6:
            print "error request Socks5Front cmd"
        elif data[0] == "\x05" and data[1] == "\x01":
            # connect
            atype_ip = '\x01'
            ip = None
            if data[3] == atype_ip:
                
                ip = data[4:8]
                print_byte(ip)
                ip = "%s.%s.%s.%s"%(ord(ip[0]), ord(ip[1]), ord(ip[2]), ord(ip[3]))
                # port = ord(data[8:10])
                port = struct.unpack("!H", data[8:10])[0]
                print "%s, %s"%(ip, port)
                self.start_target_connect(ip, port)
                # reactor.connectTCP(ip, port, Socks5EndFactory(self))
                print ""

        # if not issuSocks5Endort:
        #     print "***fetch faild"
        #     self.transport.write(rep)
        #     self.transport.loseConnection()

    def start_target_connect(self, ip, port):
        print "****start target connect"
        self.transport.pauseProducing()

        client = Socks5EndFactory(self)
        # client = self.clientProtocolFactory()
        # client.setServer(self)

        # if self.reactor is None:
        #     from twisted.internet import reactor
        #     self.reactor = reactor
        # ip = "127.0.0.1"
        # port = 1234
        print "********", dir(self)
        reactor.connectTCP(ip, port, client)

    def target_connect_ok(self, addr):
        issuSocks5Endort = True
        # addr = self.addr
        print "((((( ", addr
        rep = "\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        print type(addr.host), addr.host
        ips = []
        for i in addr.host.split("."):
            ips.append(int(i))
        s = struct.pack("!BBBBH", ips[0], ips[1], ips[2], ips[3], addr.port)
        rep = "\x05\x00\x00\x00" + s
        print repr(rep)
        self.transport.write(rep)
        self.socksok = True


    def dataReceived(self, data):
        print "=====data echo ",
        print_byte(data)
        print data
        print ""
        if not self.ishansol:
            self.hansol(data)
        elif not self.socksok:
            self.fetch_cmd(data)
        else:
            print "********data send "
            print data
            print ""
            self.socks5end.transport.write(data)
            print "****socks5end data write finish"
            # self.transport.pauseProducing()
            # self.socks5end.transport.resumeProducing()
        # print self.transport.getHost()
        # print dir(self.transport)
        # 数据已经接收完全
        # reactor.connectTCP("www.baidu.com", 443, Socks5EndFactory(data))
        # self.Socks5Endfac.send_data(data)
        # self.transport.write("xx")

class EchoFactory(protocol.Factory):
    def buildProtocol(self, addr):
        print dir(self)
        print addr
        return Socks5Front(self, addr)

    def startedConnecting(self, connector):
        print('***Started to connect.')

    def clientConnectionLost(self, connector, reason):
        print('***Lost connection.  Reason:', reason)

    def clientConnectionFailed(self, connector, reason):
        print('***Connection failed. Reason:', reason)



endpoints.serverFromString(reactor, "tcp:8080").listen(EchoFactory())
reactor.run()



# class Greeter(Protocol):
#   def sendMessage(self, msg):
#       self.transport.write("MESSAGE %s\n" % msg)
#   def gotProtocol(p):
#       p.sendMessage("Hello")
#       reactor.callLater(1, p.sendMessage, "This is sent in a second")
#       reactor.callLater(2, p.transport.loseConnection)

# point = TCP4ClientEndpoint(reactor, "localhost", 1234)
# d = connectProtocol(point, Greeter())
# d.addCallback(gotProtocol)
# reactor.run()

# self.reactor.connectTCP(host, port, clientFactory)